using Pinwheel.LSerialize;
using Pinwheel.Service;

namespace Pinwheel.Kernel
{
    public abstract partial class ServiceBase : IService
    {
        protected IMessageQueue<ServiceMessage> m_MessageQueue;

        public IMessageQueue<ServiceMessage> MessageQueue => m_MessageQueue;
        
        protected IServiceManager m_ServiceManager;
        public IServiceManager ServiceManager => m_ServiceManager;
        public string Node => m_ServiceManager.NodeName;
        
        private Dictionary<string, ApiMethod> m_ApiMap = new Dictionary<string, ApiMethod>();
        
        protected PID m_PID;
        public PID PID => m_PID;
        
        protected string m_Alias;
        public string Alias
        {
            get
            {
                if (string.IsNullOrEmpty(m_Alias))
                {
                    m_Alias = m_PID == PID.Invalid
                        ? m_ServiceManager.GetServiceName(this)
                        : m_ServiceManager.GetServiceName(PID);
                }
                return m_Alias;
            }
        }

        public ServiceStatus Status { get; internal set; }
        
        public virtual void Create(IServiceManager serviceManager, IMessageQueue<ServiceMessage> messageQueue)
        {
            Status = ServiceStatus.Bootstrap;
            m_ServiceManager = serviceManager;
            m_MessageQueue = messageQueue ?? new MessageQueue(this);
            AddComponent(new Rpc());
        }
        
        public virtual void OnRegister(PID pid)
        {
            m_PID = pid;
            Status = ServiceStatus.Registered;
        }

        public virtual void OnUnregister()
        {
            m_PID = PID.Invalid;
            Status = ServiceStatus.Invalid;
        }
        
        protected void Rename(string alias)
        {
            m_ServiceManager.RenameService(m_PID, alias);
            m_Alias = alias;
        }

        public virtual int Init()
        {
            Status = ServiceStatus.Alive;
            return 0;
        }
        
        public virtual void Signal(int signal)
        {
        }

        public virtual void Release()
        {
            Status = ServiceStatus.Stopped;
        }
                
        public virtual void Post(ServiceMessage message)
        {
            MessageQueue.Enqueue(message);
        }
        
        public virtual async ValueTask<IMessage> ReceiveAsync(ServiceMessage message)
        {
            Log.Info($"[{Node}][{Alias}]ReceiveAsync type: {message.Type}, session: {message.Session}, method: {message.Method}, message: {message.Message}, thread: {Thread.CurrentThread.ManagedThreadId}");

            IMessage result = null!;
            switch (message.Type)
            {
                case MessageType.Response:
                    if (message.Type == MessageType.Response)
                    {
                        var tcs = InvokeMessageHandler(MessageHandlerType.Receive, ref message);
                        if (null == tcs) tcs = Global.Rpc.HandleReceive(ref message);
                        if (null == tcs) throw new Exception($"[{Node}][{Alias}]ReceiveAsync get response but no request found: {message}");
                    }
                    break;
                case MessageType.Service:
                case MessageType.Client:
                    try
                    {
                        result = await InvokeApi(message.Method, message);
                    }
                    catch (Exception e)
                    {
                        Log.Exception(e);
                    }
            
                    if (message.Session > 0)
                    {
                        var response = new ServiceMessage();
                        response.SetType(MessageType.Response);
                        response.SetSession(message.Session);
                        response.SetRoute(m_PID, message.Source);
                        response.SetMethod(message.Method);
                        response.SetMessage(result);

                        if (message.Source == PID.System || message.Source == PID.Invalid)
                            Global.Rpc.HandleReceive(ref response);
                        else
                            message.Source.Send(m_ServiceManager,
                                message.Source.IsLocal ? Global.DefaultService : Cluster.Name, response);
                    }
                    break;
                default:
                    if (this is IDispatcher dispatcher)
                        dispatcher.Dispatch(message);
                    break;
            }
            
            return result;
        }

        #region Local
        
        public void Send(string alias, string method, IMessage message)
        {
            Send(alias, method, MessageType.Service, message);
        }
        
        public void Send(PID dest, string method, IMessage message)
        {
            Send(dest, method, MessageType.Service, message);
        }
        
        public void Send(string alias, string method, MessageType type, IMessage message)
        {
            var dest = m_ServiceManager.GetServicePID(alias);
            if (dest == PID.Invalid) throw new Exception($"[{Node}][{Alias}]Send Service not found: {alias}");
            Send(dest, method, type, message);
        }

        public void Send(PID dest, string method, MessageType type, IMessage message)
        {
            dest.Send(m_ServiceManager, m_PID, Global.DefaultService, method, type, message);
        }
        
        public void Send(PID source, string alias, string method, MessageType type, IMessage message)
        {
            var dest = m_ServiceManager.GetServicePID(alias);
            if (dest == PID.Invalid) throw new Exception($"[{Node}][{Alias}]Send Service not found: {alias}");
            Send(source, dest, method, type, message);
        }
        
        public void Send(PID source, PID dest, string method, MessageType type, IMessage message)
        {
            dest.Send(m_ServiceManager, source, Global.DefaultService, method, type, message);
        }
        
        // public void Send(PID source, MessageType type, byte[] buffer)
        // {
        //     var serviceMessage = LSerializer.Deserialize<ServiceMessage>(buffer);
        //     
        //     Assert.Check(serviceMessage != null, $"[{Alias}]Send buffer deserialize failed: {buffer}");
        //     Assert.Check(source == PID.System || serviceMessage.Source == source, $"[{Alias}]Send buffer deserialize source not match: {serviceMessage.Source} != {source}");
        //     Assert.Check(serviceMessage.Type == type, $"[{Alias}]Send buffer deserialize type not match: {serviceMessage.Type} != {type}");
        //     
        //     if (serviceMessage.Source != source) serviceMessage.SetRoute(source, serviceMessage.Destination);
        //     if (serviceMessage.Type != type) serviceMessage.SetType(type);
        //     
        //     serviceMessage.Destination.Send(m_ServiceManager, serviceMessage);
        // }
        
        public void Send(PID source, PID dest, ref NetPacket packet)
        {
            dest.Send(m_ServiceManager, source, ref packet);
        }

        public async ValueTask<IMessage> Call(string alias, ServiceMessage message, int timeout = 5000)
        {
            var dest = m_ServiceManager.GetServicePID(alias);
            return await dest.Call(m_ServiceManager, Global.DefaultService, message, timeout);
        }
        
        public async ValueTask<IMessage> Call(PID dest, ServiceMessage message, int timeout = 5000)
        {
            return await dest.Call(m_ServiceManager, Global.DefaultService, message, timeout);
        }
        
        public async ValueTask<IMessage> Call(string alias, string method, IMessage message, int timeout = 5000)
        {
            return await Call(alias, method, MessageType.Service, message, timeout);
        }
        
        public async ValueTask<IMessage> Call(PID dest, string method, IMessage message, int timeout = 5000)
        {
            return await Call(dest, method, MessageType.Service, message, timeout);
        }
        
        public async ValueTask<IMessage> Call(string alias, string method, MessageType type, IMessage message, int timeout = 5000)
        {
            var dest = m_ServiceManager.GetServicePID(alias);
            if (dest == PID.Invalid) throw new Exception($"[{Alias}]Call Service not found: {alias}");
            return await Call(dest, method, type, message, timeout);
        }
        
        public async ValueTask<IMessage> Call(PID dest, string alias, string method, MessageType type, IMessage message, int timeout = 5000)
        {
            return await dest.Call(m_ServiceManager, m_PID, alias, method, type, message, timeout);
        }
        
        public async ValueTask<IMessage> Call(PID dest, string method, MessageType type, IMessage message, int timeout = 5000)
        {
            return await dest.Call(m_ServiceManager, m_PID, Global.DefaultService, method, type, message, timeout);
        }
        
        public async ValueTask<IMessage> Call(PID source, PID dest, string method, MessageType type, IMessage message)
        {
            return await dest.Call(m_ServiceManager, source, Global.DefaultService, method, type, message);
        }
        
        public async ValueTask<IMessage> Call(PID source, string alias, string method, MessageType type, IMessage message)
        {
            var dest = m_ServiceManager.GetServicePID(alias);
            if (dest == PID.Invalid) throw new Exception($"[{Node}][{Alias}]Call Service not found: {alias}");
            return await dest.Call(m_ServiceManager, source, Global.DefaultService, method, type, message);
        }
        #endregion


        #region Remote
        
        public void Send(string node, string alias, string method, IMessage message)
        {
            Send(node, alias, method, MessageType.Service, message);
        }
        
        public void Send(string node, string alias, string method, MessageType type, IMessage message)
        {
            Send(Cluster.Name, "Send", type, new RemoteCall()
            {
                Node = node,
                Service = alias,
                Method = method,
                Type = type,
                Message = message
            });
        }
        
        public void Send(PID cluster, PID dest, string alias, string method, MessageType type, IMessage message)
        {
            dest.Send(m_ServiceManager, cluster, alias, method, type, message);
        } 

        public async ValueTask<IMessage> Call(string node, string alias, string method, IMessage message, int timeout = 5000)
        {
            return await Call(node, alias, method, MessageType.Service, message, timeout);
        }
        
        public async ValueTask<IMessage> Call(string node, string alias, string method, MessageType type, IMessage message, int timeout = 5000)
        {
            return await Call(Cluster.Name, "Call", type, new RemoteCall()
            {
                Node = node,
                Service = alias,
                Method = method,
                Type = type,
                Message = message
            }, timeout);
        }

        public async ValueTask<IMessage> Call(PID cluster, PID dest, string alias, string method, MessageType type, IMessage message, int timeout = 5000)
        {
            return await dest.Call(m_ServiceManager, cluster, alias, method, type, message, timeout);
        } 

        #endregion


        #region Api

        public void RegisterApi(ApiMethod handler)
        {
            m_ApiMap.TryAdd(nameof(handler), handler);
        }
        
        public void RegisterApi(string name, ApiMethod handler)
        {
            m_ApiMap.TryAdd(name, handler);
        }
        
        public void UnregisterApi(string name)
        {
            m_ApiMap.Remove(name);
        }
        
        public async ValueTask<IMessage> InvokeApi(string name, ServiceMessage message)
        {
            if (m_ApiMap.TryGetValue(name, out var handler))
            {
                return await handler(message);
            }
            throw new Exception($"[{Alias}]InvokeApi Api not found: {name}");
        }
        
        #endregion
    }
}